<html>
<head>
  <title>${pageTitle}</title>
  <style>
    body {
      margin: 0;
      font-family: Helvetica, Arial, sans-serif;
      font-size: 13px;
    }

    hr {
      border-style: solid;
    }

    input {
      margin-left: 3px;
      border-top: none;
      border-left: none;
      border-right: none;
      border-bottom: 1px solid #b8b8b8;
      outline: none;
      width: 175px;
    }

    input:focus {
      border-bottom: 1px solid #707070;
    }

    select {
      background-color: #ffffff;
      border-radius: 2px;
      padding: 3px;
    }

    #wrap {
      margin: 0 auto;
    }

    #sidebar {
      float: left;
      height: 100%;
      overflow: auto;
      position: fixed;
      width: 400px;
      background: #e8e8e8;
    }

    #sidebar input {
      background: #e8e8e8;
      border-bottom: 1px solid #858585;
    }

    #sidebar input:focus {
      border-bottom: 1px solid #393939;
    }

    #sidebar h1 {
      margin: 0;
    }

    #route {
      margin-left: 410px;
      padding-top: 10px;
      width: 70%;
    }

    #routes {
      padding-left: 0px;
    }

    #routes li {
      list-style-type: none;
      padding: 3px 3px 3px 10px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      cursor: pointer;
    }

    #routes li:hover {
      background-color: #d3d3d3;
    }

    #request-path {
      width: 500px;
    }

    .value {
      width: 800px;
    }

    .body-container {
      margin-top: 5px;
    }

    .method {
      display: inline-block;
      color: #ffffff;
      background-color: #7b7b7b;
      border-radius: 3px;
      padding: 2px 4px;
      font-size: 10.998px;
      font-weight: bold;
      line-height: 14px;
      vertical-align: baseline;
      white-space: nowrap;
      text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
      width: 4em;
      text-align: center;
    }

    .method-GET {
      background-color: #4dab58;
    }

    .method-POST {
      background-color: #cc9900;
    }

    .method-DELETE {
      background-color: #cc3300;
    }

    .path {
      display: inline-block;
    }

    .actions {
      margin-top: 20px;
    }

    button {
      text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
      background-image: linear-gradient(to bottom, #0088cc, #0044cc);
      background-repeat: repeat-x;
      border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
      padding: 4px 12px;
    }

    .actions button {
      color: #ffffff;
      background-color: #0044cc;
    }

    .parameter {
      margin-top: 3px;
    }

    .parameter input {
      vertical-align: top;
    }

    .parameter span.name {
      vertical-align: top;
      padding-right: 10px;
    }

    .required .name {
      font-weight: bold;
    }

    img.remove {
      padding-left: 5px;
    }

    ${additionalStyles}
  </style>
  <script type="text/javascript">
    var REMOVE_IMAGE_DATA = '';
    var routes = ${routesJson};

    var clearRequest = function () {
      document.getElementById('request-headers').innerHTML = createParameterHtml('request-header');
      document.getElementById('request-url-parameters').innerHTML = createParameterHtml('request-url-parameter');
      document.getElementById('request-form-datas').innerHTML = createParameterHtml('request-form-data');
      document.getElementById("response-status").innerHTML = '';
      document.getElementById("response-contentType").innerHTML = '';
      document.getElementById("response-text").innerHTML = '';
      document.getElementById("response-img").innerHTML = '';
    };

    var loadRoute = function (method, path) {
      var route = routes[method][path];
      console.log('loading route', route);
      clearRequest();
      document.getElementById('request-path').value = route.path;
      document.getElementById('request-method').value = route.method;
      var parameterTargetId = 'request-form-datas';
      var parameterItemClass = 'request-form-data';
      if (route.method == 'GET' || route.method == 'DELETE') {
        parameterTargetId = 'request-url-parameters';
        parameterItemClass = 'request-url-parameter';
      }
      var parameterHtml = '';
      var bodyParameter = null;
      route.parameters.forEach(function (parameter) {
        if (parameter.body) {
          bodyParameter = parameter;
          return;
        }
        parameterHtml += createParameterHtml(parameterItemClass, parameter.name, parameter.defaultValue, parameter.required);
      });
      parameterHtml += createParameterHtml(parameterItemClass);
      if (bodyParameter) {
        parameterHtml = createBodyParameterHtml(parameterItemClass, bodyParameter.required);
      }
      document.getElementById(parameterTargetId).innerHTML = parameterHtml;
    };

    var addHeaderParameter = function (name, value) {
      var node = createNodeFromHtml(createParameterHtml('request-header', name, value));
      document.getElementById('request-headers').appendChild(node);
      ensureBlankParameter('request-headers', true);
    };

    var addUrlParameter = function (name, value) {
      var node = createNodeFromHtml(createParameterHtml('request-url-parameter', name, value));
      document.getElementById('request-url-parameters').appendChild(node);
      ensureBlankParameter('request-url-parameters', true);
    };

    var addFormParameter = function (name, value) {
      var node = createNodeFromHtml(createParameterHtml('request-form-data', name, value));
      document.getElementById('request-form-datas').appendChild(node);
      ensureBlankParameter('request-form-datas', true);
    };

    var removeItem = function (e) {
      var caller = e.target || e.srcElement;
      var row = caller.parentNode;
      var wrapper = row.parentNode;
      wrapper.removeChild(row);
      ensureBlankParameter(wrapper.id);
    };

    var onParameterNameChange = function (e) {
      var caller = e.target || e.srcElement;
      ensureBlankParameter(caller.parentNode.parentNode.id);
    };

    var ensureBlankParameter = function (id, removeBlanks) {
      var elem = document.getElementById(id);
      var parameters = getRequestParameters(id, true);
      var hasBlank = false;
      for (var i = parameters.length - 1; i >= 0; i--) {
        var p = parameters[i];
        if (removeBlanks && !p.name && !p.value) {
          elem.removeChild(elem.childNodes[i]);
        } else if (!p.name) {
          hasBlank = true;
        }
      }
      if (!hasBlank) {
        var className = id.substr(0, id.length - 1);
        var blankHtml = createParameterHtml(className);
        var childNode = createNodeFromHtml(blankHtml);
        elem.appendChild(childNode);
      }
    };

    var createNodeFromHtml = function (html) {
      var div = document.createElement('div');
      div.innerHTML = html;
      return div.childNodes[0];
    };

    var createParameterHtml = function (className, name, value, required) {
      name = name || '';
      value = value || '';
      value = value.replace(/"/g, '&quot;');
      return '<div class="parameter ' + className + ' ' + (required ? 'required' : '') + '">' +
          '<input class="name" placeholder="Name" onkeyup="onParameterNameChange(event);" value="' + name + '">' +
          '<input class="value" placeholder="Value" value="' + value + '">' +
          '<img class="remove" onclick="removeItem(event); return false;" src="' + REMOVE_IMAGE_DATA + '"/>' +
          '</div>';
    };

    var createBodyParameterHtml = function (className, required) {
        return '<div class="parameter ' + className + ' ' + (required ? 'required' : '') + '">' +
            '<div>' +
            '<span class="name">Body Content Type</span>' +
            '<input class="value content-type" value="application/json">' +
            '</div>' +
            '<div class="body-container">' +
            '<span class="name">Body</span>' +
            '<textarea class="value" placeholder="Body Value" rows="20"></textarea>' +
            '</div>' +
            '</div>';
    };

    var sendRequest = function () {
      console.log('sending request');
      document.getElementById('response-time').innerHTML = '';
      document.getElementById('response-status').innerHTML = '';
      document.getElementById('response-contentType').innerHTML = '';
      document.getElementById('response-img').innerHTML = '';
      document.getElementById('response-text').innerHTML = 'Loading...';

      var startTime = new Date().getTime();
      var httpRequest;
      if (window.XMLHttpRequest) { // Mozilla, Safari, IE7+ ...
        httpRequest = new XMLHttpRequest();
      } else if (window.ActiveXObject) { // IE 6 and older
        httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
      }
      var requestMethod = document.getElementById('request-method').value;
      var requestPath = document.getElementById('request-path').value;
      httpRequest.onreadystatechange = function () {
        if (httpRequest.readyState == 4) {
          var endTime = new Date().getTime();
          document.getElementById('response-time').innerHTML = (endTime - startTime) + 'ms';

          document.getElementById('response-status').innerHTML = httpRequest.status;

          var contentType = httpRequest.getResponseHeader('content-type');
          document.getElementById("response-contentType").innerHTML = contentType;

          var blob = httpRequest.response;
          if (contentType && contentType.indexOf('image') == 0) {
            var img = document.createElement('img');
            img.onload = function (e) {
              window.URL.revokeObjectURL(img.src); // Clean up after yourself.
            };
            img.src = window.URL.createObjectURL(blob);
            document.getElementById('response-img').appendChild(img);
            document.getElementById('response-text').innerHTML = '';
          } else {
            var myReader = new FileReader();
            myReader.addEventListener("loadend", function (e) {
              var caller = e.target || e.srcElement;
              var responseText = caller.result;
              try {
                responseText = JSON.stringify(JSON.parse(responseText), null, 2);
              } catch (e) {
                // guess it's not json
              }
              document.getElementById('response-img').innerHTML = '';
              document.getElementById('response-text').innerHTML = responseText;
            });
            myReader.readAsText(blob);
          }
        }
      };
      var urlParameters = getRequestParameters('request-url-parameters');
      var urlParameterString = urlParameters.length > 0 ? '?' + toParameterString(urlParameters) : '';
      httpRequest.open(requestMethod, requestPath + urlParameterString, true);
      httpRequest.responseType = 'blob';

      var headerParameters = getRequestParameters('request-headers');
      headerParameters.forEach(function (headerParameter) {
        httpRequest.setRequestHeader(headerParameter.name, headerParameter.value);
      });

      var formParameters = getRequestParameters('request-form-datas');
      if (formParameters.some(function(param) {return param.body})) {
        if (formParameters.length > 1) {
          throw new Error('Cannot mix form parameters');
        }
        httpRequest.setRequestHeader('Content-Type', formParameters[0].contentType || 'application/json');
        httpRequest.send(formParameters[0].value);
      } else {
        if (requestMethod !== 'GET') {
          httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        }
        var formParameterString = toParameterString(formParameters);
        httpRequest.send(formParameterString.length > 0 ? formParameterString : null);
      }
    };

    var getRequestParameters = function (wrapperNodeId, includeBlankParameters) {
      var results = [];
      var parameterNodes = document.getElementById(wrapperNodeId).childNodes;
      for (var i = 0; i < parameterNodes.length; i++) {
        var textArea = parameterNodes[i].getElementsByTagName("textarea");
        if (textArea.length > 0) {
          var contentType = parameterNodes[i].getElementsByClassName("content-type")[0].value;
          results.push({body: true, value: textArea[0].value, contentType: contentType});
        } else {
          var name = parameterNodes[i].childNodes[0].value;
          var value = parameterNodes[i].childNodes[1].value;
          if (name || includeBlankParameters) {
            results.push({name: name, value: value});
          }
        }
      }
      return results;
    };

    var toParameterString = function (urlParameters) {
      var results = '';
      for (var i = 0; i < urlParameters.length; i++) {
        if (i > 0) {
          results += '&';
        }
        results += encodeURIComponent(urlParameters[i].name);
        results += '=';
        results += encodeURIComponent(urlParameters[i].value);
      }
      return results;
    };

    var filterRoutes = function () {
      var filter = document.getElementById('routes-filter').value.toLowerCase();
      var routes = document.getElementById('routes');
      for (var i = 0; i < routes.childNodes.length; i++) {
        var child = routes.childNodes[i];
        if (child instanceof Text) {
          continue;
        }
        var show = filter.length == 0;
        if (child.innerHTML.toLowerCase().indexOf(filter) >= 0) {
          show = true;
        }
        var style = child.getAttribute("style") || '';
        style = style.replace('display:none;', '');
        if (!show) {
          style += 'display:none;';
        }
        child.setAttribute("style", style);
      }
    };

    var stripQuotes = function (str) {
      var m;
      if (m = str.match(/^'(.*)'$/)) {
        return m[1];
      }
      if (m = str.match(/^"(.*)"$/)) {
        return m[1];
      }
      return str;
    };

    var ignoredCurlParameters = ['cookie', 'origin', 'accept-encoding', 'accept-language', 'user-agent', 'content-type', 'accept', 'referer', 'connection'];

    var parseUrlParameters = function (str) {
      var results = [];
      var urlParameters = str.split('&');
      for (i = 0; i < urlParameters.length; i++) {
        var urlParameterParts = urlParameters[i].match(/(.*?)=(.*)/);
        if (urlParameterParts) {
          results.push([urlParameterParts[1], decodeURIComponent(urlParameterParts[2])]);
        } else {
          results.push([urlParameters[i]]);
        }
      }
      return results;
    };

    var requestPathKeyUp = function () {
      var i;
      var requestPath = document.getElementById('request-path').value;
      var parameterName, parameterValue;
      var m = requestPath.match(/curl '(.*?)'(.*)/);
      if (m) {
        clearRequest();
        var method = 'GET';
        var url = m[1];
        var parameters = m[2].match(/[^\s']+|'[^']+'/g);

        if (m = url.match(/(.*?)\?(.*)/)) {
          url = m[1];
          var urlParameters = parseUrlParameters(m[2]);
          for (i = 0; i < urlParameters.length; i++) {
            parameterName = urlParameters[i][0].replace(/\\/g, '');
            parameterValue = (urlParameters[i][1] || '').replace(/\\/g, '');
            addUrlParameter(parameterName, parameterValue);
          }
        }
        document.getElementById('request-path').value = url;

        for (i = 0; i < parameters.length; i++) {
          parameters[i] = stripQuotes(parameters[i]);
          if (parameters[i] == '-H') {
            i++;
            var header = stripQuotes(parameters[i]);
            if (m = header.match(/(.*?):(.*)/)) {
              parameterName = m[1].trim();
              parameterValue = m[2].trim();
              if (ignoredCurlParameters.indexOf(parameterName.toLowerCase()) >= 0) {
                continue;
              }
              addHeaderParameter(parameterName, parameterValue);
            }
          } else if (parameters[i] == '--data') {
            i++;
            method = 'POST';
            var postData = parseUrlParameters(stripQuotes(parameters[i]));
            for (var postDataIndex = 0; postDataIndex < postData.length; postDataIndex++) {
              parameterName = postData[postDataIndex][0].replace(/\\/g, '');
              parameterValue = postData[postDataIndex][1].replace(/\\/g, '');
              addFormParameter(parameterName, parameterValue);
            }
          }
        }
        document.getElementById('request-method').value = method;
      }
    };

    ${additionalJavascript}
  </script>
</head>
<body>
<div id="wrap">
  <div id="sidebar">
    <h1>${pageTitle}</h1>

    <input id="routes-filter" onkeyup="filterRoutes();" placeholder="Filter Routes">

    <ul id="routes">
      ${routes}
    </ul>
  </div>
  <div id="route">
    <form>
      <div id="request-url">
        <input id="request-path" placeholder="URL or Curl command" onkeyup="requestPathKeyUp();">
        <select id="request-method">
          <option value="GET" selected="">GET</option>
          <option value="POST">POST</option>
          <option value="PUT">PUT</option>
          <option value="PATCH">PATCH</option>
          <option value="DELETE">DELETE</option>
          <option value="COPY">COPY</option>
          <option value="HEAD">HEAD</option>
          <option value="OPTIONS">OPTIONS</option>
          <option value="LINK">LINK</option>
          <option value="UNLINK">UNLINK</option>
          <option value="PURGE">PURGE</option>
        </select>
      </div>

      <h3>Headers</h3>

      <div id="request-headers">
      </div>

      <h3>URL Parameters</h3>

      <div id="request-url-parameters">
      </div>

      <h3>Form Data</h3>

      <div id="request-form-datas">
      </div>

      <div class="actions">
        <button onclick="try{sendRequest();}catch(err){console.log(err);} return false;">Send</button>
      </div>
    </form>
    <hr/>
    <div id="response">
      <div id="response-time"></div>
      <div id="response-status"></div>
      <div id="response-contentType"></div>
      <div id="response-img"></div>
      <pre id="response-text"></pre>
    </div>
  </div>
</div>
<script type="text/javascript">clearRequest();</script>
</body>
</html>